﻿/*
____
 L|ypps Web controls for ASP.NET

Copyright (c) 2009 Alessandro Zifiglio, http://www.Typps.com
EMail: alessandro@Typps.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
namespace Typps
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Web.Script.Serialization;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    [ToolboxBitmap(typeof(ResourceDummy), "Typps.Resources.Images.effects.bmp")]
    [DesignerAttribute(typeof(Typps.EffectsControlDesigner))]
    [ParseChildren(true),
    PersistChildren(false)]
    [DefaultProperty("EffectsList")]
    [ToolboxData("<{0}:Effects runat=\"server\"></{0}:Effects>")]
    public class Effects : Control, IScriptControl
    {
        #region Fields

        private static JavaScriptSerializer json = new JavaScriptSerializer();

        ClientScriptManager cs;
        EffectsCollection effectsValue = null;
        string[] manifestResourceNamesCacheValue;
        ScriptManager sm;

        #endregion Fields

        #region Constructors

        public Effects()
        {
            manifestResourceNamesCacheValue = typeof(Effects).Assembly.GetManifestResourceNames();
        }

        #endregion Constructors

        #region Properties

        [Browsable(false)]
        public new bool DesignMode
        {
            get
            {
                return (HttpContext.Current == null);
            }
        }

        /// <summary>
        /// Add your special effects and they shall be played on the targetcontrolid in the 
        /// order you added them. Alternatively
        /// you can call the play method clientside to manually the animation.
        /// </summary>
        [Category("Behavior"),
        Description("A collection of effects"),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        Editor(typeof(Typps.EffectsCollectionEditor), typeof(System.Drawing.Design.UITypeEditor)),
        PersistenceMode(PersistenceMode.InnerProperty)]
        public EffectsCollection EffectsList
        {
            get
            {
                if (effectsValue == null)
                    effectsValue = new EffectsCollection();
                if (IsTrackingViewState)
                    ((IStateManager)(effectsValue)).TrackViewState();
                return effectsValue;
            }
        }

        /// <summary>
        /// The effect was canceled and the animation will stop.
        /// <cc1:Effects id="Effects1" OnClientCancel="onCancel".../>
        /// And then define the clientside function onCancel eg : 
        /// function onCancel(eff){} For more, reference
        /// the official document provided by Adobe here :
        /// http://labs.adobe.com/technologies/spry/articles/effects_coding/index.html (navigate towards the
        /// last paragraph that deals with observers)
        /// </summary>
        [DefaultValue("")]
        public string OnClientCancel
        {
            get { return (string)(ViewState["OnClientCancel"] ?? string.Empty); }
            set { ViewState["OnClientCancel"] = value; }
        }

        /// <summary>
        /// Use this to register a client side event that will fire for all events 
        /// (onPreEffect,onStep,onPostEffect,onCancel,onToggle) eg :
        /// <cc1:Effects id="Effects1" OnClientObserver="onObserver".../>
        /// and then your clientside function that you define in your page will be as follows :
        /// function onObserver(notificationType, eff){} ; Note the notificationType argument,
        /// in your event handler. This will contain the notification 
        /// (onPreEffect,onStep,onPostEffect,onCancel,onToggle) that you can use in a switch statement
        /// to branch out with your code and take action accordingly based on notification.
        /// For more, reference the official document provided by Adobe here :
        /// http://labs.adobe.com/technologies/spry/articles/effects_coding/index.html (navigate towards the
        /// last paragraph that deals with observers)
        /// </summary>
        [DefaultValue("")]
        public string OnClientObserver
        {
            get { return (string)(ViewState["OnClientObserver"] ?? string.Empty); }
            set { ViewState["OnClientObserver"] = value; }
        }

        /// <summary>
        /// The animation finished all the steps and will stop.
        /// You can pass your clientside js event name to this method eg : 
        /// <cc1:Effects id="Effects1" OnClientPostEffect="onPostEffect"...
        /// And then define the clientside function onPostEffect eg : 
        /// function onPostEffect(eff){} For more, reference
        /// the official document provided by Adobe here :
        /// http://labs.adobe.com/technologies/spry/articles/effects_coding/index.html (navigate towards the
        /// last paragraph that deals with observers)
        /// </summary>
        [DefaultValue("")]
        public string OnClientPostEffect
        {
            get { return (string)(ViewState["OnClientPostEffect"] ?? string.Empty); }
            set { ViewState["OnClientPostEffect"] = value; }
        }

        /// <summary>
        /// This clientside event handler is fired when the effect is about to start the element animation.
        /// You can pass your clientside js event name to this method eg : 
        /// <cc1:Effects id="Effects1" OnClientPreEffect="onPreEffect".../>
        /// And then define the clientside function OnPreEffect eg : 
        /// function onPreEffect(eff){} For more, reference
        /// the official document provided by Adobe here :
        /// http://labs.adobe.com/technologies/spry/articles/effects_coding/index.html (navigate towards the
        /// last paragraph that deals with observers)
        /// </summary>
        [DefaultValue("")]
        public string OnClientPreEffect
        {
            get { return (string)(ViewState["OnClientPreEffect"] ?? string.Empty); }
            set { ViewState["OnClientPreEffect"] = value; }
        }

        /// <summary>
        /// The animation executed a new animation step. The code you execute here should not be time 
        /// consuming otherwise you'll affect the animation smoothness that wait at each step for 
        /// your code to finish.
        /// <cc1:Effects id="Effects1" OnClientStep="onStep".../>
        /// And then define the clientside function onStep eg : 
        /// function onStep(eff){} For more, reference
        /// the official document provided by Adobe here :
        /// http://labs.adobe.com/technologies/spry/articles/effects_coding/index.html (navigate towards the
        /// last paragraph that deals with observers)
        /// </summary>
        [DefaultValue("")]
        public string OnClientStep
        {
            get { return (string)(ViewState["OnClientStep"] ?? string.Empty); }
            set { ViewState["OnClientStep"] = value; }
        }

        /// <summary>
        /// The animation is about to start in the backward direction. 
        /// This notification will be fired only if the toggle option is on.
        /// You can pass your clientside js event name to this method eg : 
        /// <cc1:Effects id="Effects1" OnClientToggle="onToggle".../>
        /// And then define the clientside function onToggle eg : 
        /// function onToggle(eff){} For more, reference
        /// the official document provided by Adobe here :
        /// http://labs.adobe.com/technologies/spry/articles/effects_coding/index.html (navigate towards the
        /// last paragraph that deals with observers)
        /// </summary>
        [DefaultValue("")]
        public string OnClientToggle
        {
            get { return (string)(ViewState["OnClientToggle"] ?? string.Empty); }
            set { ViewState["OnClientToggle"] = value; }
        }

        /// <summary>
        /// Using AddParallelEffect causes all of the registered effects to run in parallel.
        /// In other words, the registered Effects run together.
        /// While using AddNextEffect causes all of the registered effects to run one after the other.
        /// In other words, as one effects finishes, the next registered effect will run and so on. 
        /// The default is AddParellelEffect (when using UseDefault). When having registered more than
        /// one effect, RunMode set on an Effect individually has priority. This means if you set a 
        /// runtime mode on a Fade effect explicity other than UseDefault, that will have precedence over runtime mode set directly on
        /// this control via this property.
        /// </summary>
        public EffectRunMode RunMode
        {
            get { return (EffectRunMode)(ViewState["RunMode"]??EffectRunMode.UseDefault);}
            set { ViewState["RunMode"] = value; }
        }

        /// <summary>
        /// The event on which the TargetControl gets to start playing the effect. The default is
        /// PageLoad. When NotSet is defined, you are responsible for calling the play method
        /// on this control manually ( client side) to play the animation.
        /// </summary>
        [DefaultValue(typeof(SpecialEffectActivation), "NotSet"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public SpecialEffectActivation SpecialEffectActivationOn
        {
            get { return (ViewState["SpecialEffectActivationOn"] == null) ?
                SpecialEffectActivation.NotSet :
                (SpecialEffectActivation)ViewState["SpecialEffectActivationOn"]; }
            set { (ViewState["SpecialEffectActivationOn"]) = value; }
        }

        /// <summary>
        /// Control ClientID on which to apply the effects. This can very well be the id of an html element
        /// not necessarily a ServerSide control.
        /// </summary>
        [TypeConverter(typeof(AssociatedHyperLinkConverter)),
        IDReferenceProperty,
        Category("Behavior"),
        Themeable(false),
        DefaultValue(""),
        Description("Target Control"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public string TargetControl
        {
            get
            {
                return (ViewState["TargetControl"] == null) ? string.Empty : (string)ViewState["TargetControl"];
            }
            set
            {
                this.ViewState["TargetControl"] = value;
            }
        }

        #endregion Properties

        #region Methods

        IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
        {
            return GetScriptDescriptors();
        }

        IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
        {
            return GetScriptReferences();
        }

        public void SetClientSideHandlersDescriptor(ref ScriptComponentDescriptor descriptor)
        {
            if(!string.IsNullOrEmpty(OnClientPreEffect))
                descriptor.AddEvent("onPreEffect", OnClientPreEffect);
            if (!string.IsNullOrEmpty(OnClientPostEffect))
                descriptor.AddEvent("onPostEffect", OnClientPostEffect);
            if (!string.IsNullOrEmpty(OnClientStep))
                descriptor.AddEvent("onStep", OnClientStep);
            if (!string.IsNullOrEmpty(OnClientCancel))
                descriptor.AddEvent("onCancel", OnClientCancel);
            if (!string.IsNullOrEmpty(OnClientToggle))
                descriptor.AddEvent("onToggle", OnClientToggle);
            if (!string.IsNullOrEmpty(OnClientObserver))
                descriptor.AddEvent("onObserver", OnClientObserver);
        }

        public void SetEffectsDescriptor(ref ScriptComponentDescriptor descriptor)
        {
            descriptor.AddScriptProperty("effectsList", Helper.ToJsonString(EffectsList));
        }

        public void SetRunModeDescriptor(ref ScriptComponentDescriptor descriptor)
        {
            descriptor.AddProperty("runMode", RunMode);
        }

        public void SetSpecialEffectActivationOnDescriptor(ref ScriptComponentDescriptor descriptor)
        {
            string activation = null;
            if (SpecialEffectActivationOn != SpecialEffectActivation.NotSet)
                activation = SpecialEffectActivationOn.ToString();
            descriptor.AddProperty("specialEffectActivationOn", activation);
        }

        public void SetTargetControlIdDescriptor(ref ScriptComponentDescriptor descriptor)
        {
            string controlId = null;
            if (!string.IsNullOrEmpty(TargetControl))
                controlId = TargetControl;
            descriptor.AddProperty("targetControlId", controlId);
        }

        protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptComponentDescriptor descriptor = new ScriptComponentDescriptor("SpryEffectsWrapper.Effects");
            descriptor.ID = this.ClientID;
            if (DesignMode)
                return new ScriptDescriptor[] { descriptor };
            SetEffectsDescriptor(ref descriptor);
            SetClientSideHandlersDescriptor(ref descriptor);
            SetTargetControlIdDescriptor(ref descriptor);
            SetSpecialEffectActivationOnDescriptor(ref descriptor);
            SetRunModeDescriptor(ref descriptor);
            return new ScriptDescriptor[] { descriptor };
        }

        protected virtual IEnumerable<ScriptReference> GetScriptReferences()
        {
            EnsureClientScript();
            if (cs == null) return null;
            bool isDebugMode = sm.ScriptMode == ScriptMode.Debug;
            string script1 = !isDebugMode ? "ClientScripts.spryeffects.min.js" : "ClientScripts.spryeffects.js";
            string script2 = !isDebugMode ? "ClientScripts.effects.min.js" : "ClientScripts.effects.js";

            ScriptReference reference1 = new ScriptReference();
            reference1.Path = cs.GetWebResourceUrl(typeof(ResourceDummy),
                Helper.GetManifestResourceName(script1,
                manifestResourceNamesCacheValue, isDebugMode));

            ScriptReference reference2 = new ScriptReference();
            reference2.Path = cs.GetWebResourceUrl(typeof(ResourceDummy),
                Helper.GetManifestResourceName(script2,
                manifestResourceNamesCacheValue, isDebugMode));

            return new ScriptReference[] { reference1, reference2 };
        }

        protected override void LoadViewState(object state)
        {
            if (state != null)
            {
                object[] states = (object[])state;
                base.LoadViewState(states[0]);
                ((IStateManager)EffectsList).LoadViewState(states[1]);
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            EnsureScriptManager();
            if (sm == null)
                throw new HttpException("A ScriptManager control must exist on the current page.");

            if (sm != null && sm.SupportsPartialRendering)
            {
                sm.RegisterScriptControl(this);
                sm.RegisterScriptDescriptors(this);
            }
        }

        protected override object SaveViewState()
        {
            object[] states = new object[2];
            states[0] = base.SaveViewState();
            states[1] = (effectsValue != null) ? ((IStateManager)effectsValue).SaveViewState() : null;
            return states;
        }

        protected override void TrackViewState()
        {
            base.TrackViewState();
            if (effectsValue != null)
                ((IStateManager)effectsValue).TrackViewState();
        }

        private void EnsureClientScript()
        {
            if (cs == null && Page != null)
                cs = Page.ClientScript;
        }

        private void EnsureScriptManager()
        {
            if (sm == null)
                sm = ScriptManager.GetCurrent(Page);
        }

        #endregion Methods
    }
}